/**
 * Copyright 2018 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.mvc.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;

import com.jianggujin.modulelink.util.JAssert;

/**
 * 输入输出工具
 * 
 * @author jianggujin
 *
 */
public class JIOUtils {

   public static final int BUFFER_SIZE = 4096;

   /**
    * 安全关闭
    * 
    * @param closeable
    */
   public static void safeClose(Closeable closeable) {
      if (closeable == null) {
         return;
      }
      try {
         closeable.close();
      } catch (IOException e) {
      }
   }

   /**
    * 将输入流中的数据复制到字符串
    * 
    * @param in
    * @param charset
    * @return
    * @throws IOException
    */
   public static String copyToString(InputStream in, Charset charset) throws IOException {
      JAssert.checkNotNull(in, "No InputStream specified");
      StringBuilder out = new StringBuilder();
      InputStreamReader reader = new InputStreamReader(in, charset);
      char[] buffer = new char[BUFFER_SIZE];
      int bytesRead = -1;
      while ((bytesRead = reader.read(buffer)) != -1) {
         out.append(buffer, 0, bytesRead);
      }
      safeClose(reader);
      return out.toString();
   }

   /**
    * 将字符串写入到输出流
    * 
    * @param in
    * @param charset
    * @param out
    * @throws IOException
    */
   public static void copy(String in, Charset charset, OutputStream out) throws IOException {
      JAssert.checkNotNull(in, "No input String specified");
      JAssert.checkNotNull(charset, "No charset specified");
      JAssert.checkNotNull(out, "No OutputStream specified");
      Writer writer = new OutputStreamWriter(out, charset);
      writer.write(in);
      writer.flush();
      safeClose(writer);
      safeClose(out);
   }

   /**
    * 对输入流进行处理，调用{@link InputStream#close()}对其无影响
    * 
    * @param in
    * @return
    */
   public static InputStream noneClosing(InputStream in) {
      JAssert.checkNotNull(in, "No InputStream specified");
      return new JNoneClosingInputStream(in);
   }

   /**
    * 对输出流进行处理，调用{@link InputStream#close()}对其无影响
    * 
    * @param out
    * @return
    */
   public static OutputStream noneClosing(OutputStream out) {
      JAssert.checkNotNull(out, "No OutputStream specified");
      return new JNoneClosingOutputStream(out);
   }

   public static Reader noneClosing(Reader reader) {
      JAssert.checkNotNull(reader, "No Reader specified");
      return new JNoneClosingReader(reader);
   }

   public static Writer noneClosing(Writer writer) {
      JAssert.checkNotNull(writer, "No Writer specified");
      return new JNoneClosingWriter(writer);
   }

   /**
    * 复制文件
    * 
    * @param in
    * @param out
    * @return
    * @throws IOException
    */
   public static int copy(File in, File out) throws IOException {
      JAssert.checkNotNull(in, "No input File specified");
      JAssert.checkNotNull(out, "No output File specified");
      return copy(new BufferedInputStream(new FileInputStream(in)),
            new BufferedOutputStream(new FileOutputStream(out)));
   }

   /**
    * 复制字节数组到文件
    * 
    * @param in
    * @param out
    * @throws IOException
    */
   public static void copy(byte[] in, File out) throws IOException {
      JAssert.checkNotNull(in, "No input byte array specified");
      JAssert.checkNotNull(out, "No output File specified");
      ByteArrayInputStream inStream = new ByteArrayInputStream(in);
      OutputStream outStream = new BufferedOutputStream(new FileOutputStream(out));
      copy(inStream, outStream);
   }

   /**
    * 文件转换为字节数组
    * 
    * @param in
    * @return
    * @throws IOException
    */
   public static byte[] copyToByteArray(File in) throws IOException {
      JAssert.checkNotNull(in, "No input File specified");
      return copyToByteArray(new BufferedInputStream(new FileInputStream(in)));
   }

   /**
    * 复制字节数组到输出流，最后关闭流
    * 
    * @param in
    * @param out
    * @throws IOException
    */
   public static void copy(byte[] in, OutputStream out) throws IOException {
      JAssert.checkNotNull(in, "No input byte array specified");
      JAssert.checkNotNull(out, "No OutputStream specified");
      try {
         out.write(in);
      } finally {
         try {
            out.close();
         } catch (IOException ex) {
         }
      }
   }

   /**
    * 从输入流读取数据到字节数组
    * 
    * @param in
    * @return
    * @throws IOException
    */
   public static byte[] copyToByteArray(InputStream in) throws IOException {
      ByteArrayOutputStream out = new ByteArrayOutputStream(BUFFER_SIZE);
      copy(in, out);
      return out.toByteArray();
   }

   /**
    * 将输入流数据写入到输出流
    * 
    * @param in
    *           输入流
    * @param out
    *           输出流
    * @throws IOException
    */
   public static int copy(InputStream in, OutputStream out) throws IOException {
      final byte[] buffer = new byte[BUFFER_SIZE];
      int byteCount = 0;
      int len = -1;
      while ((len = in.read(buffer)) != -1) {
         out.write(buffer, 0, len);
         byteCount += len;
      }
      out.flush();
      safeClose(in);
      safeClose(out);
      return byteCount;
   }

   /**
    * 从输入流读取数据并写入到输出流
    * 
    * @param in
    * @param out
    * @return
    * @throws IOException
    */
   public static int copy(Reader in, Writer out) throws IOException {
      JAssert.checkNotNull(in, "No Reader specified");
      JAssert.checkNotNull(out, "No Writer specified");
      int byteCount = 0;
      char[] buffer = new char[BUFFER_SIZE];
      int bytesRead = -1;
      while ((bytesRead = in.read(buffer)) != -1) {
         out.write(buffer, 0, bytesRead);
         byteCount += bytesRead;
      }
      out.flush();
      safeClose(in);
      safeClose(out);
      return byteCount;
   }

   /**
    * 将字符串写入到输出流，最后关闭流
    * 
    * @param in
    * @param out
    * @throws IOException
    */
   public static void copy(String in, Writer out) throws IOException {
      JAssert.checkNotNull(in, "No input String specified");
      JAssert.checkNotNull(out, "No Writer specified");
      out.write(in);
      safeClose(out);
   }

   /**
    * 从输入流读取数据到字符串，最后关闭流
    * 
    * @param in
    * @return
    * @throws IOException
    */
   public static String copyToString(Reader in) throws IOException {
      StringWriter out = new StringWriter();
      copy(in, out);
      return out.toString();
   }

   private static class JNoneClosingInputStream extends FilterInputStream {

      public JNoneClosingInputStream(InputStream in) {
         super(in);
      }

      @Override
      public void close() throws IOException {
      }
   }

   private static class JNoneClosingOutputStream extends FilterOutputStream {

      public JNoneClosingOutputStream(OutputStream out) {
         super(out);
      }

      @Override
      public void write(byte[] b, int off, int let) throws IOException {
         out.write(b, off, let);
      }

      @Override
      public void close() throws IOException {
      }
   }

   private static class JNoneClosingReader extends Reader {
      private Reader reader;

      public JNoneClosingReader(Reader reader) {
         this.reader = reader;
      }

      @Override
      public void close() throws IOException {
      }

      @Override
      public int read(char[] cbuf, int off, int len) throws IOException {
         return this.reader.read(cbuf, off, len);
      }
   }

   private static class JNoneClosingWriter extends Writer {
      private Writer writer;

      public JNoneClosingWriter(Writer writer) {
         this.writer = writer;
      }

      @Override
      public void close() throws IOException {
      }

      @Override
      public void write(char[] cbuf, int off, int len) throws IOException {
         this.write(cbuf, off, len);
      }

      @Override
      public void flush() throws IOException {
         this.writer.flush();
      }
   }
}
